Se ejecutan los paquetes necesarios:

  1. readxl: Para leer archivos de datos
  2. rpart: Para ajustar modelos de árboles de decisión
  3. randomForest: Para ejecutar modelos de Random Forest
  4. gbm: Para ejecutar Boosting de árboles
  5. caret: Para entrenar modelos
  6. psych: Para una mejor visualización de las estadisticas descriptivas
  7. plotly: Para generar graficos
library(readxl)
library(rpart)
library(rpart.plot)
library(ipred)
library(caret)
library(randomForest)
library(gbm)
library(plotly)
library(psych)
library(reshape2)
library(imager)
library(data.table)

1 Metodologia de analisis

Como primer paso se debe importar los datos a la herramienta de nuestra preferencia, en este caso R, con el fin de familiarizarse con ellos y realizar un analisis exploratorio inicial

HR <- read_excel("C:/Users/n.mejia10/Desktop/Prueba/WA_Fn-UseC_-HR-Employee-Attrition.xlsx") ##Se importan los datos

1.1 Tratamiento de variables

Es necesario especificar al programa la naturaleza de cada una de las variables con el fin de no hacr analisis erroneos. Por esta razon variables presentes en la base de datos como: “Education”, “Attrition”, “Gender” se transforman en variables categoricas o factores.

Factores<-c("BusinessTravel", "Education", "EducationField", "EnvironmentSatisfaction", "Gender", "JobInvolvement", "JobLevel", "JobRole", "JobSatisfaction", "MaritalStatus", "Over18", "OverTime", "PerformanceRating", "RelationshipSatisfaction", "StockOptionLevel", "WorkLifeBalance", "Attrition", "Department")
HR[Factores]<- lapply(HR[Factores],factor)
HR<-HR[,-c(9:10, 22, 27)]

1.2 Analisis exploratorio inicial

Se seleccionan unicamente las variables numericas y se obtienen estadisticas descriptivas con el fin de ver sus tendencias y distribuciones.

ED<-HR %>% select(which(sapply(., is.numeric)))
describe(ED)
                        vars    n     mean      sd  median  trimmed     mad  min
Age                        1 1470    36.92    9.14    36.0    36.47    8.90   18
DailyRate                  2 1470   802.49  403.51   802.0   803.83  510.01  102
DistanceFromHome           3 1470     9.19    8.11     7.0     8.08    7.41    1
HourlyRate                 4 1470    65.89   20.33    66.0    66.02   26.69   30
MonthlyIncome              5 1470  6502.93 4707.96  4919.0  5667.24 3260.24 1009
MonthlyRate                6 1470 14313.10 7117.79 14235.5 14286.48 9201.76 2094
NumCompaniesWorked         7 1470     2.69    2.50     2.0     2.36    1.48    0
PercentSalaryHike          8 1470    15.21    3.66    14.0    14.80    2.97   11
TotalWorkingYears          9 1470    11.28    7.78    10.0    10.37    5.93    0
TrainingTimesLastYear     10 1470     2.80    1.29     3.0     2.72    1.48    0
YearsAtCompany            11 1470     7.01    6.13     5.0     5.99    4.45    0
YearsInCurrentRole        12 1470     4.23    3.62     3.0     3.85    4.45    0
YearsSinceLastPromotion   13 1470     2.19    3.22     1.0     1.48    1.48    0
YearsWithCurrManager      14 1470     4.12    3.57     3.0     3.77    4.45    0
                          max range  skew kurtosis     se
Age                        60    42  0.41    -0.41   0.24
DailyRate                1499  1397  0.00    -1.21  10.52
DistanceFromHome           29    28  0.96    -0.23   0.21
HourlyRate                100    70 -0.03    -1.20   0.53
MonthlyIncome           19999 18990  1.37     0.99 122.79
MonthlyRate             26999 24905  0.02    -1.22 185.65
NumCompaniesWorked          9     9  1.02     0.00   0.07
PercentSalaryHike          25    14  0.82    -0.31   0.10
TotalWorkingYears          40    40  1.11     0.91   0.20
TrainingTimesLastYear       6     6  0.55     0.48   0.03
YearsAtCompany             40    40  1.76     3.91   0.16
YearsInCurrentRole         18    18  0.92     0.47   0.09
YearsSinceLastPromotion    15    15  1.98     3.59   0.08
YearsWithCurrManager       17    17  0.83     0.16   0.09

Se puede observar que el empleado medio de la compañia tiene 36 años, gana 802 dolares diarios, ha recibido un promedio de 3 capacitaciones el año pasado, tiene 2.1 años desde su ultima promocion y lleva 7 años en la compañia.

2 Modelos

Una vez se realizó un analisis exploratorio inicial, se procede a responder la pregunta de interes: ¿Cuales son las variables que mas influyen en la satisfacción con respecto al trabajo de los empleados de la empresa. Para responder la pregunta se entrenaran modelos basados en árboles con el fi nde predecir la variable de satisfacción.

Esto debido a la ventaja que poseen los árboles en que permiten, despues de ejecutado el modelo, cuantificar la importancia de cada variable en la construccion del mismo. Esto mediante el coeficiente de pureza o gini.

En este sentido se ajustaran 3 modelos basado en árboles: Un RandomForest, Un Bagging y un Boosting con el fin de encontrar para cada uno las variables ams relevenate y comparar los resultados.

Para realizar los modelos, se sepran los datos en 2 sets: Uno de Entrenamiento y otro de Test

set.seed(123)
ID<-sample(1:nrow(HR),size=nrow(HR)*0.7)
train<-HR[ID,] #Selecciona el 70% de las filas
test<-HR[-ID,] #Selecciona el 30% restante de filas

2.1 CART

Se realiza un árbol de clasificación em primer lugar para aprovechar de su estructura grafica y empezar con una idea de las vara¿iables por las cuales parte en las ramas superiores. Puede observarse que variables de slario y tipo de trabajo resultan las mas importantes en la construcción del árbol.

## Se ajusta un CART con los parametro base
set.seed(123)
tree <- rpart(JobSatisfaction ~ ., data = train,  method="class", control=rpart.control(xval=10, cp=1e-04))
prp(tree)

x<-varImp(tree)
setorder(x,Overall)
x

2.2 Bagging

Como siguiente paso se implementa un Bagging y se busca la importancia de las variables. Baggin se implementa con la libreria RandomForest debido a que es un caso especial de RF donde se utilizan todas las variables en cada nuevo árbol. En este caso, resultan importantes las variables de salario, seguido por la edad y la distancia que debe recorrer de la casa al trabajo.

Bag <- randomForest(JobSatisfaction ~ ., data = train, ntree=2000, mtry=ncol(train)-1)
x<-varImp(Bag)
setorder(x,Overall)
x
PlotImp<-varImpPlot(Bag, type=2, main = "Bagging")

2.3 Random Forest

Ahora se implementa un modelo de RAndom Forest, el cual se calibra mediante validacion cruzada, donde se encuentra que se deben tener en cuenta 2 variables en la creacion de cada árbol. Al ejecutar el modelo calibrado, se encuentra que variables de salario, edad, rol de trabajo, el numero de años que el empleado lleva trabajando y la distancia del trabajo a la casa resultan importantes en la definicion de la satisfacción.

fitControl <- trainControl(method = "cv",number = 10)
RFFit <- train(JobSatisfaction ~ ., data = train,  method="rf", trControl = fitControl, tuneLenght=10)
RandomF <- randomForest(JobSatisfaction ~ ., data = train, ntree=2000, mtry=2)
x<-varImp(RandomF)
setorder(x,Overall)
x
PlotImp<-varImpPlot(RandomF, type=2, main = "Random Forest")

2.4 Boosting

Por ultimo, se ejecuta un modelo de Boosting, el cual mediante validación cruzada calibra el numero de árboles a utilizar. Este modelo entrega que las variables de Salario diario y mensual, junto con el Rol que el empleado lleva a cabo son determinantes en la satisfacción por el trabajo.

set.seed(123)
HR$BSat<-ifelse(HR$JobSatisfaction==1,1,ifelse(HR$JobSatisfaction==2,2,ifelse(HR$JobSatisfaction==3,3,4)))
                                          
HR1<-HR[,-15]
train<-HR1[ID,] #Selecciona el 70% de las filas
test<-HR1[-ID,]
set.seed(123)
Boost<-gbm(BSat~.,data=train,distribution="multinomial",n.trees=2000,interaction.depth=2, cv.folds = 10)
NArboles<-gbm.perf(Boost, method="cv")  

set.seed(123)
Boost<-gbm(BSat~.,data=train,distribution="multinomial",n.trees=NArboles,interaction.depth=2)
summary(Boost) #Influencia relativa de cada predictor del modelo

3 Conclusiones

Al obtener las importancias relativas en cada uno de los modelos se procede a realizar una tablas que condense los reultados y una grafica para una mejor visualizacion. Al pasar el cursor por la grafica se puede observar cada uno de los puntos a que variable equivale, cual es su valor y cual es su modelo (Morado: Boosting, Rosado: CART, Verde: Bagging, Azul: Random Forest)

Results <- read_excel("C:/Users/n.mejia10/Desktop/Prueba/Results.xlsx")
Results
Results2<-reshape2::melt(Results, id.var='Variable')
colnames(Results2)<-c("Variable","Model","Value")
Plt<-ggplot(Results2, aes(x=Variable, y=Value, col=Model, group=1)) + geom_line()+theme(axis.text.x = element_blank(),legend.position="none")
ggplotly(Plt)

En la grafica se puede observar que variables referentes al salario del empleado como: DailyRate, MonthlyRate, HourlyRate, PercentSalaryHike como variables del tipo de trabajo que realiza el empleado como: JobRole, el numero total de años que lleva trabajando (TotalWorkingYears) y la distancia que debe recorrer de la casa al trabajo (DistanceFromHome) resultan de gran importancia a en todos los modelos lo que indica que estas variables son las que mas ayudan a definir la satisfacción de un empleado en su trabajo.

4 DashBoard

Con esto en mente se desarollo un DashBoard que permite llevar control de variables de Salario, Rol, Balance Trabajo/Vida personal, Porcentaje de Abandono por cada uno de los departamentos de la empresa. El dashboard puede verse en el archivo adjunto app.R

Antes de correr el DashBoard se deben instalar las librerias: Shiny, Shiny DashBoard, scales, ggplot2, ggthemes, dyplr

El dashboard permite seleccionar el departamento a lo que muestra: a) En la parte superior 4 indicadores que son: 1. El porcentaje de abandono para dicho departamento 2. Un promedio de la satisfaccion del ambiente de trabajo 3. Promedio del salario diario que ganan los empleados del departamento 4. Un prmedio del numero de años que llevan los empleados del departamento en la compañia

im<-load.image("C:/Users/n.mejia10/Pictures/Dash1.PNG")
plot(im, axes=FALSE)

  1. 4 Graficas que relacionan variables importantes con la satisfaccion
  1. la distribucion de la Satisfaccion por rol de trabajo
  2. El porcentaje de abandono por nivel de satisfacción en el departamento
  3. La distribucion del salario diario por nivel de satisfacción
  4. El nivel de stisfaccion por cada nivel de balance entre la vida personal y el trabajo.
im<-load.image("C:/Users/n.mejia10/Pictures/Dash2.PNG")
plot(im,axes=FALSE)

LS0tDQp0aXRsZTogIlBydWViYSBUZWNuaWNhIg0KYXV0aG9yOiAiTmljb2zhcyBNZWrtYSBNYXJ07W5leiINCmRhdGU6ICJOb3ZpZW1icmUgMjUsIDIwMTgiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IHVuaXRlZA0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gJ0M6L1VzZXJzL24ubWVqaWExMC9EZXNrdG9wL1BydWViYScsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UgKQ0KYGBgDQpTZSBlamVjdXRhbiBsb3MgcGFxdWV0ZXMgbmVjZXNhcmlvczoNCg0KMS4gcmVhZHhsOiBQYXJhIGxlZXIgYXJjaGl2b3MgZGUgZGF0b3MNCjIuIHJwYXJ0OiBQYXJhIGFqdXN0YXIgbW9kZWxvcyBkZSDhcmJvbGVzIGRlIGRlY2lzafNuIA0KMy4gcmFuZG9tRm9yZXN0OiBQYXJhIGVqZWN1dGFyIG1vZGVsb3MgZGUgUmFuZG9tIEZvcmVzdA0KNC4gZ2JtOiBQYXJhIGVqZWN1dGFyIEJvb3N0aW5nIGRlIOFyYm9sZXMNCjUuIGNhcmV0OiBQYXJhIGVudHJlbmFyIG1vZGVsb3MNCjYuIHBzeWNoOiBQYXJhIHVuYSBtZWpvciB2aXN1YWxpemFjafNuIGRlIGxhcyBlc3RhZGlzdGljYXMgZGVzY3JpcHRpdmFzDQo3LiBwbG90bHk6IFBhcmEgZ2VuZXJhciBncmFmaWNvcw0KDQpgYGB7ciB3YXJuaW5nfQ0KbGlicmFyeShyZWFkeGwpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShpcHJlZCkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoZ2JtKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KHBzeWNoKQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkoaW1hZ2VyKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KYGBgDQoNCg0KDQojIE1ldG9kb2xvZ2lhIGRlIGFuYWxpc2lzDQoNCkNvbW8gcHJpbWVyIHBhc28gc2UgZGViZSBpbXBvcnRhciBsb3MgZGF0b3MgYSBsYSBoZXJyYW1pZW50YSBkZSBudWVzdHJhIHByZWZlcmVuY2lhLCBlbiBlc3RlIGNhc28gUiwgY29uIGVsIGZpbiBkZSBmYW1pbGlhcml6YXJzZSBjb24gZWxsb3MgeSByZWFsaXphciB1biBhbmFsaXNpcyBleHBsb3JhdG9yaW8gaW5pY2lhbA0KDQpgYGB7ciBjYXJzfQ0KDQpIUiA8LSByZWFkX2V4Y2VsKCJDOi9Vc2Vycy9uLm1lamlhMTAvRGVza3RvcC9QcnVlYmEvV0FfRm4tVXNlQ18tSFItRW1wbG95ZWUtQXR0cml0aW9uLnhsc3giKSAjI1NlIGltcG9ydGFuIGxvcyBkYXRvcw0KYGBgDQoNCiMjIFRyYXRhbWllbnRvIGRlIHZhcmlhYmxlcw0KRXMgbmVjZXNhcmlvIGVzcGVjaWZpY2FyIGFsIHByb2dyYW1hIGxhIG5hdHVyYWxlemEgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyBjb24gZWwgZmluIGRlIG5vIGhhY3IgYW5hbGlzaXMgZXJyb25lb3MuIFBvciBlc3RhIHJhem9uIHZhcmlhYmxlcyBwcmVzZW50ZXMgZW4gbGEgYmFzZSBkZSBkYXRvcyBjb21vOiAiRWR1Y2F0aW9uIiwgIkF0dHJpdGlvbiIsICJHZW5kZXIiIHNlIHRyYW5zZm9ybWFuIGVuIHZhcmlhYmxlcyBjYXRlZ29yaWNhcyBvIGZhY3RvcmVzLg0KDQpgYGB7cn0NCg0KDQpGYWN0b3JlczwtYygiQnVzaW5lc3NUcmF2ZWwiLCAiRWR1Y2F0aW9uIiwgIkVkdWNhdGlvbkZpZWxkIiwgIkVudmlyb25tZW50U2F0aXNmYWN0aW9uIiwgIkdlbmRlciIsICJKb2JJbnZvbHZlbWVudCIsICJKb2JMZXZlbCIsICJKb2JSb2xlIiwgIkpvYlNhdGlzZmFjdGlvbiIsICJNYXJpdGFsU3RhdHVzIiwgIk92ZXIxOCIsICJPdmVyVGltZSIsICJQZXJmb3JtYW5jZVJhdGluZyIsICJSZWxhdGlvbnNoaXBTYXRpc2ZhY3Rpb24iLCAiU3RvY2tPcHRpb25MZXZlbCIsICJXb3JrTGlmZUJhbGFuY2UiLCAiQXR0cml0aW9uIiwgIkRlcGFydG1lbnQiKQ0KDQpIUltGYWN0b3Jlc108LSBsYXBwbHkoSFJbRmFjdG9yZXNdLGZhY3RvcikNCg0KSFI8LUhSWywtYyg5OjEwLCAyMiwgMjcpXQ0KYGBgDQoNCg0KIyMgQW5hbGlzaXMgZXhwbG9yYXRvcmlvIGluaWNpYWwNClNlIHNlbGVjY2lvbmFuIHVuaWNhbWVudGUgbGFzIHZhcmlhYmxlcyBudW1lcmljYXMgeSBzZSBvYnRpZW5lbiBlc3RhZGlzdGljYXMgZGVzY3JpcHRpdmFzIGNvbiBlbCBmaW4gZGUgdmVyIHN1cyB0ZW5kZW5jaWFzIHkgZGlzdHJpYnVjaW9uZXMuDQpgYGB7cn0NCkVEPC1IUiAlPiUgc2VsZWN0KHdoaWNoKHNhcHBseSguLCBpcy5udW1lcmljKSkpDQpkZXNjcmliZShFRCkNCmBgYA0KIFNlIHB1ZWRlIG9ic2VydmFyIHF1ZSBlbCBlbXBsZWFkbyBtZWRpbyBkZSBsYSBjb21wYfFpYSB0aWVuZSAzNiBh8W9zLCBnYW5hIDgwMiBkb2xhcmVzIGRpYXJpb3MsIGhhIHJlY2liaWRvIHVuIHByb21lZGlvIGRlIDMgY2FwYWNpdGFjaW9uZXMgZWwgYfFvIHBhc2FkbywgdGllbmUgMi4xIGHxb3MgZGVzZGUgc3UgdWx0aW1hIHByb21vY2lvbiB5IGxsZXZhIDcgYfFvcyBlbiBsYSBjb21wYfFpYS4NCg0KIyBNb2RlbG9zDQoNClVuYSB2ZXogc2UgcmVhbGl68yB1biBhbmFsaXNpcyBleHBsb3JhdG9yaW8gaW5pY2lhbCwgc2UgcHJvY2VkZSBhIHJlc3BvbmRlciBsYSBwcmVndW50YSBkZSBpbnRlcmVzOiC/Q3VhbGVzIHNvbiBsYXMgdmFyaWFibGVzIHF1ZSBtYXMgaW5mbHV5ZW4gZW4gbGEgc2F0aXNmYWNjafNuIGNvbiByZXNwZWN0byBhbCB0cmFiYWpvIGRlIGxvcyBlbXBsZWFkb3MgZGUgbGEgZW1wcmVzYS4NClBhcmEgcmVzcG9uZGVyIGxhIHByZWd1bnRhIHNlIGVudHJlbmFyYW4gbW9kZWxvcyBiYXNhZG9zIGVuIOFyYm9sZXMgY29uIGVsIGZpIG5kZSBwcmVkZWNpciBsYSB2YXJpYWJsZSBkZSBzYXRpc2ZhY2Np824uIA0KDQpFc3RvIGRlYmlkbyBhIGxhIHZlbnRhamEgcXVlIHBvc2VlbiBsb3Mg4XJib2xlcyBlbiBxdWUgcGVybWl0ZW4sIGRlc3B1ZXMgZGUgZWplY3V0YWRvIGVsIG1vZGVsbywgY3VhbnRpZmljYXIgbGEgaW1wb3J0YW5jaWEgZGUgY2FkYSB2YXJpYWJsZSBlbiBsYSBjb25zdHJ1Y2Npb24gZGVsIG1pc21vLiBFc3RvIG1lZGlhbnRlIGVsIGNvZWZpY2llbnRlIGRlIHB1cmV6YSBvIGdpbmkuDQoNCkVuIGVzdGUgc2VudGlkbyBzZSBhanVzdGFyYW4gMyBtb2RlbG9zIGJhc2FkbyBlbiDhcmJvbGVzOiAgVW4gUmFuZG9tRm9yZXN0LCBVbiBCYWdnaW5nIHkgdW4gQm9vc3RpbmcgY29uIGVsIGZpbiBkZSBlbmNvbnRyYXIgcGFyYSBjYWRhIHVubyBsYXMgdmFyaWFibGVzIGFtcyByZWxldmVuYXRlIHkgY29tcGFyYXIgbG9zIHJlc3VsdGFkb3MuDQoNClBhcmEgcmVhbGl6YXIgbG9zIG1vZGVsb3MsIHNlIHNlcHJhbiBsb3MgZGF0b3MgZW4gMiBzZXRzOiBVbm8gZGUgRW50cmVuYW1pZW50byB5IG90cm8gZGUgVGVzdA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCklEPC1zYW1wbGUoMTpucm93KEhSKSxzaXplPW5yb3coSFIpKjAuNykNCnRyYWluPC1IUltJRCxdICNTZWxlY2Npb25hIGVsIDcwJSBkZSBsYXMgZmlsYXMNCnRlc3Q8LUhSWy1JRCxdICNTZWxlY2Npb25hIGVsIDMwJSByZXN0YW50ZSBkZSBmaWxhcw0KYGBgDQojIyBDQVJUDQoNCg0KU2UgcmVhbGl6YSB1biDhcmJvbCBkZSBjbGFzaWZpY2FjafNuIGVtIHByaW1lciBsdWdhciBwYXJhIGFwcm92ZWNoYXIgZGUgc3UgZXN0cnVjdHVyYSBncmFmaWNhIHkgZW1wZXphciBjb24gdW5hIGlkZWEgZGUgbGFzIHZhcmG/aWFibGVzIHBvciBsYXMgY3VhbGVzIHBhcnRlIGVuIGxhcyByYW1hcyBzdXBlcmlvcmVzLiBQdWVkZSBvYnNlcnZhcnNlIHF1ZSB2YXJpYWJsZXMgZGUgc2xhcmlvIHkgdGlwbyBkZSB0cmFiYWpvIHJlc3VsdGFuIGxhcyBtYXMgaW1wb3J0YW50ZXMgZW4gbGEgY29uc3RydWNjafNuIGRlbCDhcmJvbC4NCmBgYHtyfQ0KIyMgU2UgYWp1c3RhIHVuIENBUlQgY29uIGxvcyBwYXJhbWV0cm8gYmFzZQ0Kc2V0LnNlZWQoMTIzKQ0KdHJlZSA8LSBycGFydChKb2JTYXRpc2ZhY3Rpb24gfiAuLCBkYXRhID0gdHJhaW4sICBtZXRob2Q9ImNsYXNzIiwgY29udHJvbD1ycGFydC5jb250cm9sKHh2YWw9MTAsIGNwPTFlLTA0KSkNCnBycCh0cmVlKQ0KeDwtdmFySW1wKHRyZWUpDQpzZXRvcmRlcih4LE92ZXJhbGwpDQp4DQoNCmBgYA0KDQojIyBCYWdnaW5nDQoNCkNvbW8gc2lndWllbnRlIHBhc28gc2UgaW1wbGVtZW50YSB1biBCYWdnaW5nIHkgc2UgYnVzY2EgbGEgaW1wb3J0YW5jaWEgZGUgbGFzIHZhcmlhYmxlcy4gQmFnZ2luIHNlIGltcGxlbWVudGEgY29uIGxhIGxpYnJlcmlhIFJhbmRvbUZvcmVzdCBkZWJpZG8gYSBxdWUgZXMgdW4gY2FzbyBlc3BlY2lhbCBkZSBSRiBkb25kZSBzZSB1dGlsaXphbiB0b2RhcyBsYXMgdmFyaWFibGVzIGVuIGNhZGEgbnVldm8g4XJib2wuIEVuIGVzdGUgY2FzbywgcmVzdWx0YW4gaW1wb3J0YW50ZXMgbGFzIHZhcmlhYmxlcyBkZSBzYWxhcmlvLCBzZWd1aWRvIHBvciBsYSBlZGFkIHkgbGEgZGlzdGFuY2lhIHF1ZSBkZWJlIHJlY29ycmVyIGRlIGxhIGNhc2EgYWwgdHJhYmFqby4NCg0KYGBge3IgZmlnMSwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQ0KQmFnIDwtIHJhbmRvbUZvcmVzdChKb2JTYXRpc2ZhY3Rpb24gfiAuLCBkYXRhID0gdHJhaW4sIG50cmVlPTIwMDAsIG10cnk9bmNvbCh0cmFpbiktMSkNCng8LXZhckltcChCYWcpDQpzZXRvcmRlcih4LE92ZXJhbGwpDQp4DQpQbG90SW1wPC12YXJJbXBQbG90KEJhZywgdHlwZT0yLCBtYWluID0gIkJhZ2dpbmciKQ0KDQpgYGANCg0KDQoNCg0KIyNSYW5kb20gRm9yZXN0DQoNCkFob3JhIHNlIGltcGxlbWVudGEgdW4gbW9kZWxvIGRlIFJBbmRvbSBGb3Jlc3QsIGVsIGN1YWwgc2UgY2FsaWJyYSBtZWRpYW50ZSB2YWxpZGFjaW9uIGNydXphZGEsIGRvbmRlIHNlIGVuY3VlbnRyYSBxdWUgc2UgZGViZW4gdGVuZXIgZW4gY3VlbnRhIDIgdmFyaWFibGVzIGVuIGxhIGNyZWFjaW9uIGRlIGNhZGEg4XJib2wuIEFsIGVqZWN1dGFyIGVsIG1vZGVsbyBjYWxpYnJhZG8sIHNlIGVuY3VlbnRyYSBxdWUgdmFyaWFibGVzIGRlIHNhbGFyaW8sIGVkYWQsIHJvbCBkZSB0cmFiYWpvLCBlbCBudW1lcm8gZGUgYfFvcyBxdWUgZWwgZW1wbGVhZG8gbGxldmEgdHJhYmFqYW5kbyB5IGxhIGRpc3RhbmNpYSBkZWwgdHJhYmFqbyBhIGxhIGNhc2EgcmVzdWx0YW4gaW1wb3J0YW50ZXMgZW4gbGEgZGVmaW5pY2lvbiBkZSBsYSBzYXRpc2ZhY2Np824uDQoNCmBgYHtyIGZpZzIsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0NCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsbnVtYmVyID0gMTApDQoNClJGRml0IDwtIHRyYWluKEpvYlNhdGlzZmFjdGlvbiB+IC4sIGRhdGEgPSB0cmFpbiwgIG1ldGhvZD0icmYiLCB0ckNvbnRyb2wgPSBmaXRDb250cm9sLCB0dW5lTGVuZ2h0PTEwKQ0KDQpSYW5kb21GIDwtIHJhbmRvbUZvcmVzdChKb2JTYXRpc2ZhY3Rpb24gfiAuLCBkYXRhID0gdHJhaW4sIG50cmVlPTIwMDAsIG10cnk9MikNCng8LXZhckltcChSYW5kb21GKQ0Kc2V0b3JkZXIoeCxPdmVyYWxsKQ0KeA0KUGxvdEltcDwtdmFySW1wUGxvdChSYW5kb21GLCB0eXBlPTIsIG1haW4gPSAiUmFuZG9tIEZvcmVzdCIpDQoNCmBgYA0KIyMgQm9vc3RpbmcNCg0KUG9yIHVsdGltbywgc2UgZWplY3V0YSB1biBtb2RlbG8gZGUgQm9vc3RpbmcsIGVsIGN1YWwgbWVkaWFudGUgdmFsaWRhY2nzbiBjcnV6YWRhIGNhbGlicmEgZWwgbnVtZXJvIGRlIOFyYm9sZXMgYSB1dGlsaXphci4gRXN0ZSBtb2RlbG8gZW50cmVnYSBxdWUgbGFzIHZhcmlhYmxlcyBkZSBTYWxhcmlvIGRpYXJpbyB5IG1lbnN1YWwsIGp1bnRvIGNvbiBlbCBSb2wgcXVlIGVsIGVtcGxlYWRvIGxsZXZhIGEgY2FibyBzb24gZGV0ZXJtaW5hbnRlcyBlbiBsYSBzYXRpc2ZhY2Np824gcG9yIGVsIHRyYWJham8uDQpgYGB7cn0NCg0Kc2V0LnNlZWQoMTIzKQ0KSFIkQlNhdDwtaWZlbHNlKEhSJEpvYlNhdGlzZmFjdGlvbj09MSwxLGlmZWxzZShIUiRKb2JTYXRpc2ZhY3Rpb249PTIsMixpZmVsc2UoSFIkSm9iU2F0aXNmYWN0aW9uPT0zLDMsNCkpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpIUjE8LUhSWywtMTVdDQp0cmFpbjwtSFIxW0lELF0gI1NlbGVjY2lvbmEgZWwgNzAlIGRlIGxhcyBmaWxhcw0KdGVzdDwtSFIxWy1JRCxdDQoNCg0Kc2V0LnNlZWQoMTIzKQ0KQm9vc3Q8LWdibShCU2F0fi4sZGF0YT10cmFpbixkaXN0cmlidXRpb249Im11bHRpbm9taWFsIixuLnRyZWVzPTIwMDAsaW50ZXJhY3Rpb24uZGVwdGg9MiwgY3YuZm9sZHMgPSAxMCkNCg0KTkFyYm9sZXM8LWdibS5wZXJmKEJvb3N0LCBtZXRob2Q9ImN2IikgIA0KDQpzZXQuc2VlZCgxMjMpDQpCb29zdDwtZ2JtKEJTYXR+LixkYXRhPXRyYWluLGRpc3RyaWJ1dGlvbj0ibXVsdGlub21pYWwiLG4udHJlZXM9TkFyYm9sZXMsaW50ZXJhY3Rpb24uZGVwdGg9MikNCnN1bW1hcnkoQm9vc3QpICNJbmZsdWVuY2lhIHJlbGF0aXZhIGRlIGNhZGEgcHJlZGljdG9yIGRlbCBtb2RlbG8NCg0KYGBgDQoNCiMgQ29uY2x1c2lvbmVzDQpBbCBvYnRlbmVyIGxhcyBpbXBvcnRhbmNpYXMgcmVsYXRpdmFzIGVuIGNhZGEgdW5vIGRlIGxvcyBtb2RlbG9zIHNlIHByb2NlZGUgYSByZWFsaXphciB1bmEgdGFibGFzIHF1ZSBjb25kZW5zZSBsb3MgcmV1bHRhZG9zIHkgdW5hIGdyYWZpY2EgcGFyYSB1bmEgbWVqb3IgdmlzdWFsaXphY2lvbi4gQWwgcGFzYXIgZWwgY3Vyc29yIHBvciBsYSBncmFmaWNhIHNlIHB1ZWRlIG9ic2VydmFyIGNhZGEgdW5vIGRlIGxvcyBwdW50b3MgYSBxdWUgdmFyaWFibGUgZXF1aXZhbGUsIGN1YWwgZXMgc3UgdmFsb3IgeSBjdWFsIGVzIHN1IG1vZGVsbyAoTW9yYWRvOiBCb29zdGluZywgUm9zYWRvOiBDQVJULCBWZXJkZTogQmFnZ2luZywgQXp1bDogUmFuZG9tIEZvcmVzdCkNCg0KYGBge3IgZmlnMywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9N30NClJlc3VsdHMgPC0gcmVhZF9leGNlbCgiQzovVXNlcnMvbi5tZWppYTEwL0Rlc2t0b3AvUHJ1ZWJhL1Jlc3VsdHMueGxzeCIpDQpSZXN1bHRzDQoNClJlc3VsdHMyPC1yZXNoYXBlMjo6bWVsdChSZXN1bHRzLCBpZC52YXI9J1ZhcmlhYmxlJykNCmNvbG5hbWVzKFJlc3VsdHMyKTwtYygiVmFyaWFibGUiLCJNb2RlbCIsIlZhbHVlIikNCg0KUGx0PC1nZ3Bsb3QoUmVzdWx0czIsIGFlcyh4PVZhcmlhYmxlLCB5PVZhbHVlLCBjb2w9TW9kZWwsIGdyb3VwPTEpKSArIGdlb21fbGluZSgpK3RoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpDQoNCmdncGxvdGx5KFBsdCkNCg0KDQpgYGANCg0KRW4gbGEgZ3JhZmljYSBzZSBwdWVkZSBvYnNlcnZhciBxdWUgdmFyaWFibGVzIHJlZmVyZW50ZXMgYWwgc2FsYXJpbyBkZWwgZW1wbGVhZG8gY29tbzogRGFpbHlSYXRlLCBNb250aGx5UmF0ZSwgSG91cmx5UmF0ZSwgUGVyY2VudFNhbGFyeUhpa2UgY29tbyB2YXJpYWJsZXMgZGVsIHRpcG8gZGUgdHJhYmFqbyBxdWUgcmVhbGl6YSBlbCBlbXBsZWFkbyBjb21vOiBKb2JSb2xlLCBlbCBudW1lcm8gdG90YWwgZGUgYfFvcyBxdWUgbGxldmEgdHJhYmFqYW5kbyAoVG90YWxXb3JraW5nWWVhcnMpIHkgbGEgZGlzdGFuY2lhIHF1ZSBkZWJlIHJlY29ycmVyIGRlIGxhIGNhc2EgYWwgdHJhYmFqbyAoRGlzdGFuY2VGcm9tSG9tZSkgIHJlc3VsdGFuIGRlIGdyYW4gaW1wb3J0YW5jaWEgYSBlbiB0b2RvcyBsb3MgbW9kZWxvcyBsbyBxdWUgaW5kaWNhIHF1ZSBlc3RhcyB2YXJpYWJsZXMgc29uIGxhcyBxdWUgbWFzIGF5dWRhbiBhIGRlZmluaXIgbGEgc2F0aXNmYWNjafNuIGRlIHVuIGVtcGxlYWRvIGVuIHN1IHRyYWJham8uDQoNCiMgRGFzaEJvYXJkDQoNCkNvbiBlc3RvIGVuIG1lbnRlIHNlIGRlc2Fyb2xsbyB1biBEYXNoQm9hcmQgcXVlIHBlcm1pdGUgbGxldmFyIGNvbnRyb2wgZGUgdmFyaWFibGVzIGRlIFNhbGFyaW8sIFJvbCwgQmFsYW5jZSBUcmFiYWpvL1ZpZGEgcGVyc29uYWwsIFBvcmNlbnRhamUgZGUgQWJhbmRvbm8gcG9yIGNhZGEgdW5vIGRlIGxvcyBkZXBhcnRhbWVudG9zIGRlIGxhIGVtcHJlc2EuIEVsIGRhc2hib2FyZCBwdWVkZSB2ZXJzZSBlbiBlbCBhcmNoaXZvIGFkanVudG8gYXBwLlINCg0KQW50ZXMgZGUgY29ycmVyIGVsIERhc2hCb2FyZCBzZSBkZWJlbiBpbnN0YWxhciBsYXMgbGlicmVyaWFzOiBTaGlueSwgU2hpbnkgIERhc2hCb2FyZCwgc2NhbGVzLCBnZ3Bsb3QyLCBnZ3RoZW1lcywgZHlwbHINCg0KRWwgZGFzaGJvYXJkIHBlcm1pdGUgc2VsZWNjaW9uYXIgZWwgZGVwYXJ0YW1lbnRvIGEgbG8gcXVlIG11ZXN0cmE6IA0KYSkgRW4gbGEgcGFydGUgc3VwZXJpb3IgNCBpbmRpY2Fkb3JlcyBxdWUgc29uOiANCiAgMS4gRWwgcG9yY2VudGFqZSBkZSBhYmFuZG9ubyBwYXJhIGRpY2hvIGRlcGFydGFtZW50bw0KICAyLiBVbiAgcHJvbWVkaW8gZGUgbGEgc2F0aXNmYWNjaW9uIGRlbCBhbWJpZW50ZSBkZSB0cmFiYWpvDQogIDMuIFByb21lZGlvIGRlbCBzYWxhcmlvIGRpYXJpbyBxdWUgZ2FuYW4gbG9zIGVtcGxlYWRvcyBkZWwgZGVwYXJ0YW1lbnRvDQogIDQuIFVuIHBybWVkaW8gZGVsIG51bWVybyBkZSBh8W9zIHF1ZSBsbGV2YW4gbG9zIGVtcGxlYWRvcyBkZWwgZGVwYXJ0YW1lbnRvIGVuIGxhIGNvbXBh8WlhDQogIA0KICANCiAgDQogIA0KYGBge3J9DQppbTwtbG9hZC5pbWFnZSgiQzovVXNlcnMvbi5tZWppYTEwL1BpY3R1cmVzL0Rhc2gxLlBORyIpDQpwbG90KGltLCBheGVzPUZBTFNFKQ0KDQpgYGANCg0KYikgNCBHcmFmaWNhcyBxdWUgcmVsYWNpb25hbiB2YXJpYWJsZXMgaW1wb3J0YW50ZXMgY29uIGxhIHNhdGlzZmFjY2lvbg0KICAxLiBsYSBkaXN0cmlidWNpb24gZGUgbGEgU2F0aXNmYWNjaW9uIHBvciByb2wgZGUgdHJhYmFqbw0KICAyLiBFbCBwb3JjZW50YWplIGRlIGFiYW5kb25vIHBvciBuaXZlbCBkZSBzYXRpc2ZhY2Np824gZW4gZWwgZGVwYXJ0YW1lbnRvDQogIDMuIExhIGRpc3RyaWJ1Y2lvbiBkZWwgc2FsYXJpbyBkaWFyaW8gcG9yIG5pdmVsIGRlIHNhdGlzZmFjY2nzbg0KICA0LiBFbCBuaXZlbCBkZSBzdGlzZmFjY2lvbiBwb3IgY2FkYSBuaXZlbCBkZSBiYWxhbmNlIGVudHJlIGxhIHZpZGEgcGVyc29uYWwgeSBlbCB0cmFiYWpvLg0KDQpgYGB7cn0NCmltPC1sb2FkLmltYWdlKCJDOi9Vc2Vycy9uLm1lamlhMTAvUGljdHVyZXMvRGFzaDIuUE5HIikNCnBsb3QoaW0sYXhlcz1GQUxTRSkNCg0KYGBgDQoNCg==